﻿using System;
using System.IO;
using System.Data;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Drawing;
using rlTodayMakeSkin.Project;
using rlTodayMakeSkin.Project.XmlElement;
using rlTodayMakeSkin.Project.XmlElement.Properties;
using rlTodayMakeSkin.Project.XmlElement.XmlSubElements;
using System.Reflection;
using System.Text.RegularExpressions;

namespace rlTodayMakeSkin.GUI
{
    /// <summary>
    /// Manages the list of properties in main window
    /// </summary>
    public sealed class PropertiesWindow
    {
        /*******************************
           FIELDS         
         ******************************/
        private ListView lvProp;
        private Label lblPropertyDescription;
        private rlTodayProject curPrj;
        private XmlElementPropertiesList curPropList;

        /*each of this control will show on value cell of the property list row selected
          to permits user to change property value*/
        private TextBox txtStrings; //to change string value
        private TextBox txtNumbers; //to change integer value
        private ComboBox cbMultiValue;    //to choose from a list of values
        private ComboBox cbRegKeys;

        //control's names to change properties value
        private const string CHANGE_VALUE_STRINGS = "txtStrings";
        private const string CHANGE_VALUE_NUMBERS = "txtNumbers";
        private const string CHANGE_VALUE_MULTI = "cbMulti";
        private const string CHANGE_VALUE_REGS = "cbRegKeys";

        private const int PROP_NAME_COL = 0;
        private const int PROP_VALUE_COL = 1;

        private SelectedProperty selectedProp; //represents selected property in the list
        private FontFamily[] FONT_NAMES;

        /*******************************
         CONSTRUCTORS
         ******************************/
        public PropertiesWindow(ListView lv, Label lblPropDescr)
        {
            lvProp = lv;
            lblPropertyDescription = lblPropDescr;
            SelectedControlsManager.ChangedSelection += new SelectedControlsManager.ChangedSelectionEvent(updateProperiesList);
            //lvProp.LostFocus += new EventHandler(LostFocus);
            lvProp.MouseUp += new MouseEventHandler(mouse_Up);

            //create control to change properties value
            txtStrings = initValueTB(CHANGE_VALUE_STRINGS);
            txtNumbers = initValueTB(CHANGE_VALUE_NUMBERS);
            cbMultiValue = initValueCB(CHANGE_VALUE_MULTI);
            cbRegKeys = initValueREG(CHANGE_VALUE_REGS);
            FONT_NAMES = FontFamily.GetFamilies(lvProp.CreateGraphics());
        }


        /*******************************
         PUBLIC METHODS
         ******************************/
        /// <summary>
        /// Clears list and fills it again with new properties
        /// </summary>
        /// <param name="list">list of properties to be showed.</param>
        public void updateProperiesList(XmlElementPropertiesList list)
        {
            lvProp.Items.Clear();
            curPropList = list;
            foreach (XmlElementProperty p in list.GetPropertiesList())
            {
                addProperty(p);
            }
        }

        /// <summary>
        /// Sets active project
        /// </summary>
        /// <param name="prj">project now active</param>
        public void setCurrentProject(rlTodayProject prj)
        {
            curPrj = prj;
        }

        /// <summary>
        /// Clears properties list
        /// </summary>
        public void unselect()
        {
            if (selectedProp != null)
            {
                selectedProp.Hide();
                selectedProp = null;
            }
            lvProp.Items.Clear();
            lblPropertyDescription.CreateGraphics().Clear(lblPropertyDescription.BackColor);
        }

        /// <summary>
        /// Shows new value for this property in the list
        /// </summary>
        /// <param name="pName">property name</param>
        /// <param name="value">new property value</param>
        public void updatePropertyValue(string pName, object value)
        {
            ListViewItem i = lvProp.FindItemWithText(pName, false, 0);
            i.SubItems[PROP_VALUE_COL].Text = value.ToString();
            XmlElementProperty p = (XmlElementProperty)i.Tag;
            p.Value = value;
            curPrj.IsModified = true;
        }


        /*******************************
         PRIVATE METHODS
         ******************************/
        /// <summary>
        /// Adds an item in the properies list that represents
        /// a property of selcted skin control
        /// </summary>
        /// <param name="p">a single property to add in the list</param>
        private void addProperty(XmlElementProperty p)
        {
            ListViewItem i = null;
            i = new ListViewItem();
            if (p.Value == null) return;
            if (p.PropertyType == PropertyTypes.COMPOSITE)
            {
                addCompositeProperty(p);
            }
            else
            {
                setItem(i, p);
                lvProp.Items.Add(i);
            }

        }

        /// <summary>
        /// Adds to the list composite property values
        /// </summary>
        /// <returns>a ListViewGroup to add to the property list</returns>
        private void addCompositeProperty(XmlElementProperty p)
        {
            ListViewItem iProp = new ListViewItem();
            ListViewItem.ListViewSubItem iPropName = new ListViewItem.ListViewSubItem();
            iPropName.Font = new Font(lvProp.Font, FontStyle.Bold);
            iPropName.Text = p.Name;
            iProp.SubItems[PROP_NAME_COL] = iPropName;
            iProp.SubItems.Add("");
            iProp.Tag = p;
            iProp.ForeColor = Color.Blue;
            lvProp.Items.Add(iProp);

            PropertyInfo[] pies = p.Value.GetType().GetProperties();
            //add each sub property in list view
            foreach(PropertyInfo pi in pies){
                iProp = new ListViewItem();
                setItem(iProp, XmlElementPropertiesList.GetElementProperty(pi, p.Value));
                //if property isn't readonly sets forecolor to blue
                if (iProp.ForeColor.Equals(Color.Black)) iProp.ForeColor = Color.DarkBlue;
                lvProp.Items.Add(iProp);
            }
        }

        /// <summary>
        /// Creates a new textbox to change value in the properties list
        /// </summary>
        /// <param name="tbName">name of the new textbox</param>
        /// <returns>a new textbox</returns>
        private TextBox initValueTB(string tbName)
        {
            TextBox txt = new TextBox();
            txt.Name = tbName;
            txt.BorderStyle = BorderStyle.None;
            txt.Font = lvProp.Font;
            txt.KeyDown += new KeyEventHandler(keyPress);
            txt.LostFocus += new EventHandler(lostFocus);
            txt.Visible = false;
            txt.Margin = new Padding(0,0,0,0);
            lvProp.Controls.Add(txt);
            return txt;
        }

        /// <summary>
        /// Creates a new combobox to change boolean value in the properties list
        /// </summary>
        /// <param name="tbName">name of the new combo box</param>
        /// <returns>a new combo box</returns>
        private ComboBox initValueCB(string cbName)
        {
            ComboBox cb = new ComboBox();
            cb.Name = cbName;
            cb.Font = new Font(lvProp.Font.FontFamily, 9);
            cb.LostFocus += new EventHandler(lostFocus);
            cb.KeyUp += new KeyEventHandler(keyPress);
            cb.Margin = new Padding(0);
            cb.Visible = false;
            lvProp.Controls.Add(cb);
            return cb;
        }

        /// <summary>
        /// Creates a new combobox to insert known registry key name or a custom new
        /// </summary>
        /// <param name="tbName">name of the new combo box</param>
        /// <returns>a new combo box</returns>
        private ComboBox initValueREG(string cbName)
        {
            ComboBox cb = initValueCB(cbName);
            DataSet dsRegKeys = new DataSet();
            string pathRegKeys = Application.StartupPath + @"\rtrdk\RegistryKeys.xml";
            cb.Items.Clear();
            //if finds RegistryKeys file loads known key name from it
            if(System.IO.File.Exists(pathRegKeys)){
                dsRegKeys.ReadXml(pathRegKeys);
                //cb.SelectedValueChanged += new EventHandler(cb_SelectedValueChanged);
                cb.SelectedIndexChanged += new EventHandler(cb_SelectedIndexChanged);
                cb.DisplayMember = "Name";
                cb.ValueMember = "Key";
                cb.DataSource = dsRegKeys.Tables[0];
            }
            return cb;
        }

        private void cb_SelectedIndexChanged(object sender, EventArgs e)
        //invocated when change the value of a registry key combo box
        {
            ComboBox cb = (ComboBox)sender;
            cb.Text = cb.SelectedValue.ToString();
            lostFocus(cb, new EventArgs());
        }

        private void mouse_Up(object sender, MouseEventArgs e)
        /*Event manager called when user click on property list. Shows a control
         to change property value*/
        {
            ListViewItem i = lvProp.GetItemAt(e.X, e.Y);
            if (i == null) return;
            XmlElementProperty p = (XmlElementProperty)i.Tag;
            setPropertyDescription(p.Name, p.Description);
            if (!p.IsReadOnly)
            { //does nothing if this is a readonly property
                showPropertyChanger(i, p);
            }
            
        }

        private void lostFocus(object sender, EventArgs arg){
        //hides control and tests if property value is changed
            if (selectedProp == null) return; //does nothing if changer was hidden
            selectedProp.changer.Visible = false;
            object value = selectedProp.property.Value;

            if (!selectedProp.changer.Text.Equals(value.ToString()))
            {
                setProperty(selectedProp.property, selectedProp.changer.Text); //change the value of the local property
                setItem(lvProp.FindItemWithText(selectedProp.property.Name), selectedProp.property); //change the value of the ListViewItem
                SelectedControlsManager.GetManager().changePropertyValue(selectedProp.property); //change the property value of the selected controls
                updateProperiesList(curPropList);
                curPrj.IsModified = true;
            }
        }

        private void keyPress(object sender, KeyEventArgs k){
        //invocated by a changer control when a key is pressed on it.
            if (k.KeyCode == Keys.Return)
                lostFocus(sender, null);
            else if (k.KeyCode == Keys.Escape)
            {
                selectedProp.Hide();
                selectedProp = null;
            }
        }


        /// <summary>
        /// Sets the content of a list item in acorrding with 
        /// passed property
        /// </summary>
        /// <param name="i">ListViewItem to fill</param>
        /// <param name="p">a XmlElementProperty</param>
        /// <remarks>it doesn't add item in the list view</remarks>
        private void setItem(ListViewItem i, XmlElementProperty p)
        {

            i.SubItems[PROP_NAME_COL].Text = p.Name;
            ListViewItem.ListViewSubItem sub = null;
            //gets the sub item with the value
            if (i.SubItems.Count > PROP_VALUE_COL)
                sub = i.SubItems[PROP_VALUE_COL];
            else
            {
                sub = new ListViewItem.ListViewSubItem();
                i.SubItems.Add(sub);
            }
            if (p.PropertyType == PropertyTypes.COLOR)
            {
                i.UseItemStyleForSubItems = false;
                sub.BackColor = (Color)p.Value;
                sub.ForeColor = sub.BackColor;
                sub.Text = " ";
            }
            else
            {
                sub.Text = p.Value.ToString();
            }
            i.Tag = p;
            if (p.IsReadOnly) i.ForeColor = Color.Gray;
        }

        /// <summary>
        /// Draws the name of the selected property and its description
        /// in the label below the ListView
        /// </summary>
        /// <param name="propName">Name of the selected property</param>
        /// <param name="description">Description of the selected property</param>
        private void setPropertyDescription(string propName, string description)
        {
            Color back = lblPropertyDescription.BackColor;
            Graphics g = lblPropertyDescription.CreateGraphics();
            Font f = lblPropertyDescription.Font;
            g.Clear(back);
            float HeightName = g.MeasureString(propName, f).Height;
            float WidthDescr = lblPropertyDescription.Width;
            float HeightDescr = lblPropertyDescription.Height - HeightName - 2;
            g.DrawString(propName, new Font(f, FontStyle.Bold), Brushes.Black, new Point(0, 0));
            g.DrawString(description, f, Brushes.Black, new RectangleF(0, HeightName + 2, WidthDescr, HeightDescr));
        }

        /// <summary>
        /// Shows a control to change the value of selected property in the list view
        /// </summary>
        /// <param name="p">selected property to change</param>
        private void showPropertyChanger(ListViewItem i, XmlElementProperty p)
        {
            selectedProp = new SelectedProperty();
            selectedProp.property = p;
            switch (p.PropertyType)
            {
                case PropertyTypes.STRING:
                case PropertyTypes.DATE_FORMAT:
                case PropertyTypes.TIME_FORMAT:
                    selectedProp.changer = txtStrings;
                    txtStrings.Text = p.Value.ToString();
                    selectedProp.Show(lvProp.Columns[PROP_NAME_COL].Width + 2, i.Position.Y);
                    break;
                case PropertyTypes.REGISTRY_VALUE:
                    string keyValue = p.Value.ToString();
                    DataTable dt = (DataTable)cbRegKeys.DataSource;
                    selectedProp.changer = cbRegKeys;
                    if (dt.Select("Name='" + keyValue + "'").Length == 0 && keyValue != "[REG_KEY]")
                    {
                        //it's typed a key not contained in the list. Add it.
                        dt.Rows.Add(keyValue, keyValue);
                    }
                    cbRegKeys.Text = keyValue;
                    selectedProp.Show(lvProp.Columns[PROP_NAME_COL].Width + 2, i.Position.Y);
                    break;
                case PropertyTypes.INTEGER:
                case PropertyTypes.FONT_SIZE:
                    selectedProp.changer = txtNumbers;
                    txtNumbers.Text = p.Value.ToString();
                    selectedProp.Show(lvProp.Columns[PROP_NAME_COL].Width + 2, i.Position.Y);
                    break;
                case PropertyTypes.BOOLEAN:
                    selectedProp.changer = cbMultiValue;
                    prepareBoolValues(cbMultiValue);
                    cbMultiValue.Text = p.Value.ToString();
                    selectedProp.Show(lvProp.Columns[PROP_NAME_COL].Width + 2, i.Position.Y);
                    break;
                case PropertyTypes.ALIGNMENT:
                    prepareAlignValues(cbMultiValue);
                    cbMultiValue.Text = p.Value.ToString();
                    selectedProp.changer = cbMultiValue;
                    selectedProp.Show(lvProp.Columns[PROP_NAME_COL].Width + 2, i.Position.Y - 3);
                    break;
                case PropertyTypes.FILE_IMAGE:
                    OpenFileDialog of = new OpenFileDialog();
                    selectedProp.changer = new Control();
                    of.Filter = "Image files | *.gif;*.png;*.bmp;*.jpg";
                    of.ShowDialog();
                    if (of.FileName == "") break;
                    selectedProp.changer.Text = of.FileName;
                    lostFocus(selectedProp.changer, new EventArgs());
                    break;
                case PropertyTypes.COLOR:
                    ColorDialog cd = new ColorDialog();
                    Color cl;
                    cd.ShowDialog();
                    selectedProp.changer = new Control();
                    if (cd.Color.IsEmpty) break; //if no color is selected
                    cl = cd.Color;
                    selectedProp.changer.Text = cl.R.ToString() + ',' + cl.G.ToString() + ',' + cl.B.ToString();
                    lostFocus(selectedProp.changer, new EventArgs());
                    break;
                case PropertyTypes.FONT_WEIGTH:
                    prepareWeigthValues(cbMultiValue);
                    cbMultiValue.Text = p.Value.ToString();
                    selectedProp.changer = cbMultiValue;
                    selectedProp.Show(lvProp.Columns[PROP_NAME_COL].Width + 2, i.Position.Y - 3);
                    break;
                case PropertyTypes.FONT_NAME:
                    prepareFontNameValue(cbMultiValue);
                    selectedProp.changer = cbMultiValue;
                    cbMultiValue.Text = p.Value.ToString();
                    selectedProp.Show(lvProp.Columns[PROP_NAME_COL].Width + 2, i.Position.Y - 3);
                    break;
            }
        }

        /// <summary>
        /// Converts the newValue in the type of the property value
        /// and assignes it
        /// </summary>
        /// <param name="p">property whose change value</param>
        /// <param name="newValue">newValue</param>
        private void setProperty(XmlElementProperty p, string newValue){
            switch (p.PropertyType)
            {
                case PropertyTypes.INTEGER:
                case PropertyTypes.FONT_SIZE:
                    p.Value = int.Parse(newValue);
                    break;
                case PropertyTypes.FILE_IMAGE:
                    if (File.Exists(curPrj.Path + p.Value.ToString()))
                        File.Delete(curPrj.Path + p.Value.ToString());
                    File.Copy(newValue, curPrj.Path + newValue.Substring(newValue.LastIndexOf('\\')),true);
                    p.Value = newValue.Substring(newValue.LastIndexOf('\\') + 1);
                    break;
                case PropertyTypes.FONT_NAME:
                case PropertyTypes.STRING:
                case PropertyTypes.REGISTRY_VALUE:
                case PropertyTypes.EXECUTABLE_NAME:
                case PropertyTypes.DATE_FORMAT:
                case PropertyTypes.TIME_FORMAT:
                case PropertyTypes.ALIGNMENT:
                case PropertyTypes.FONT_WEIGTH:
                    p.Value = newValue;
                    break;
                case PropertyTypes.COLOR:
                    //newValue is in the format r,g,b
                    int r = 0, g = 0, b = 0;
                    Regex re = new Regex(@"[\d]+");
                    Match m = re.Match(newValue);
                    r = int.Parse(m.Value);
                    m = m.NextMatch();
                    g = int.Parse(m.Value);
                    m = m.NextMatch();
                    b = int.Parse(m.Value);
                    p.Value = Color.FromArgb(r, g, b);
                    break;
            }
        }

        private void prepareBoolValues(ComboBox cb)
        {
            cb.Items.Clear();
            cb.Items.Add("true");
            cb.Items.Add("false");
        }

        private void prepareAlignValues(ComboBox cb)
        {
            cb.Items.Clear();
            cb.Items.Add("left");
            cb.Items.Add("center");
            cb.Items.Add("right");
        }

        private void prepareWeigthValues(ComboBox cb)
        {
            cb.Items.Clear();
            cb.Items.Add("normal");
            cb.Items.Add("bold");
        }

        private void prepareFontNameValue(ComboBox cb)
        {
            cb.Items.Clear();
            foreach (FontFamily fm in FONT_NAMES)
            {
                cb.Items.Add(fm.Name);
            }
        }

        /**********************
         INNER CLASSES
         **********************/

        /// <summary>
        /// represents selected property in the list
        /// </summary>
        private class SelectedProperty
        {
            public Control changer;
            public XmlElementProperty property;
            private bool blnSelected = false;

            public void Show(int x, int y) {
                changer.Location = new Point(x, y);
                changer.Visible = blnSelected = true;
                changer.Select();
            }
            public void Hide() { changer.Visible = blnSelected = false; }
            public bool isSelected { get { return blnSelected; } }
        }


    }//end class
}
